/*
  S.M.A.C.K - An operating system kernel
  Copyright (C) 2010,2011 Mattias Holm and Kristian Rietveld
  For licensing and a full list of authors of the kernel, see the files
  COPYING and AUTHORS.
*/

#include <timer.h>
#include <process.h>
#include <heap.h>
#include <slaballoc.h>

static slab_t timer_slab = SLAB_INITIALISER(struct timer_entry_t);
static struct heap_t timer_heap;

/* system ticks will be incremented on each call of the timer interrupt
 * handler.  Currently, the value increments each CLOCK_RATE of a second.
 * Or every CLOCK_RATE_MS ms.
 */
static uint32_t system_ticks = 0;

void
timer_init(void)
{
  heap_init(&timer_heap);

  hw_timer_init();
}

/* Timeout is specified in msec */
void
timer_register_timeout(uint32_t        timeout,
                       timeout_func_t  func,
                       void           *func_data)
{
  struct timer_entry_t *new_entry;

  new_entry = slab_alloc(&timer_slab);
  new_entry->deadline = timer_get_system_ticks() + timeout / CLOCK_RATE_MS;
  new_entry->func = func;
  new_entry->func_data = func_data;

  heap_insert(&timer_heap, new_entry, new_entry->deadline);
}

void
timer_deregister_timeout(timeout_func_t  func,
                         void           *func_data)
{
  /* FIXME */
}

uint32_t
timer_get_system_ticks(void)
{
  return system_ticks;
}

void
timer_call_handler(void)
{
  uint32_t systime;
  struct timer_entry_t *top;
  proc_t *current_process = proc_current();

  /* FIXME: need overflow protection? */
  system_ticks++;

  if (!heap_peek(&timer_heap))
    {
      proc_tick(current_process);
      return;
    }

  top = heap_peek(&timer_heap);

  systime = timer_get_system_ticks();

  /* FIXME: I don't really like the loop, can it be done in a better way? */
  /* FIXME: Also, we need overflow protection here as well. */
  while (systime >= top->deadline)
    {
      heap_remove(&timer_heap);

      if (top->func)
        (* top->func) (top->func_data);

      slab_free(top);

      if (!heap_peek(&timer_heap))
        break;

      top = heap_peek(&timer_heap);
    }

  /* If we already switched process, don't tick which could cause
   * another process switch.
   */
  if (current_process == proc_current())
    proc_tick(current_process);
}
